package top.cardone.web.support;

import com.google.common.base.Charsets;
import com.google.gson.Gson;
import lombok.Setter;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.io.Resource;
import org.springframework.web.util.WebUtils;
import top.cardone.cache.Cache;
import top.cardone.context.ApplicationContextHolder;
import top.cardone.core.CodeException;
import top.cardone.core.util.func.Func0;
import top.cardone.core.util.func.Func1;
import top.cardone.core.util.func.Func2;
import top.cardone.web.FuncBind;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Map;

/**
 * Func 绑定支持
 */
public class FuncBindSupport {
	/**
	 * api 配置根目录
	 */
	@Setter
	private String apiConfigRoot;

	/**
	 * api 静态根目录
	 */
	@Setter
	private String apiStaticRoot;

	/**
	 * 多个对象键名
	 */
	@Setter
	private String objectsKey = "datas";

	/**
	 * 单个基单类型键名
	 */
	@Setter
	private String objectKey = "data";

	/**
	 * func 执行
	 *
	 * @param request
	 * @return
	 */
	public Object func(HttpServletRequest request) {
		return this.func(request, request.getServletPath(), null, true);
	}

	public Object func(HttpServletRequest request, boolean cache) {
		return this.func(request, request.getServletPath(), null, cache);
	}

	public Object func(HttpServletRequest request, Object returnObject) {
		return this.func(request, request.getServletPath(), returnObject, true);
	}

	public Object func(HttpServletRequest request, Object returnObject, boolean cache) {
		return this.func(request, request.getServletPath(), returnObject, cache);
	}

	public Object func(HttpServletRequest request, String beanId, Object returnObject, boolean cache) {
		FuncBind funcBind = cache ? ApplicationContextHolder.getBean(Cache.class).get(FuncBindSupport.class.getName(), beanId, () -> this.get(beanId)) : this.get(beanId);

		if (returnObject != null || funcBind.getFunc() == null) {
			return this.postHandle(request, beanId, returnObject, funcBind);
		}

		Object input = ApplicationContextHolder.getBean(WebSupport.class).getObject(funcBind.getParameterType(), request);

		returnObject = funcBind.getFunc().func(input);

		return this.postHandle(request, beanId, returnObject, funcBind);
	}

	/**
	 * 设置 func
	 *
	 * @param funcBind func 绑定对象
	 * @param beanId
	 * @return
	 */
	private FuncBind get(FuncBind funcBind, String beanId) {
		Func1<Object, Object> func = ApplicationContextHolder.getBean(Func1.class, beanId);

		if (func == null) {
			return funcBind;
		}

		funcBind.setFunc(func);

		for (Method method : func.getClass().getDeclaredMethods()) {
			if (!"func".equals(method.getName())) {
				continue;
			}

			Class<?> parameterType = method.getParameterTypes()[0];

			if (!java.lang.Object.class.equals(parameterType)) {
				funcBind.setParameterType(parameterType);

				return funcBind;
			}
		}

		return funcBind;
	}

	/**
	 * 获取 func 绑定
	 *
	 * @param beanId
	 * @return
	 */
	private FuncBind get(String beanId) {
		try {
			String apiConfigFilePath = this.apiConfigRoot + beanId;

			String apiConfigFileExtension = FilenameUtils.getExtension(beanId);

			if (!"json".equals(apiConfigFileExtension)) {
				apiConfigFilePath += ".json";
			}

			Resource resource = ApplicationContextHolder.getApplicationContext().getResource(apiConfigFilePath);

			if (!resource.exists()) {
				return this.get(new FuncBind(), beanId);
			}

			try (InputStream is = resource.getInputStream()) {
				String apiConfigJson = IOUtils.toString(is, Charsets.UTF_8);

				if (StringUtils.isBlank(apiConfigJson)) {
					apiConfigJson = "{}";
				}

				FuncBind funcBind = ApplicationContextHolder.getBean(Gson.class).fromJson(apiConfigJson, FuncBind.class);

				return this.get(funcBind, beanId);
			}
		} catch (IOException e) {
			throw new CodeException("000013", new String[]{e.getMessage()}, e);
		}
	}

	/**
	 * 执行后
	 *
	 * @param request
	 * @param servletPath
	 * @param returnObject
	 * @param funcBind
	 * @return
	 */
	private Object postHandle(HttpServletRequest request, String servletPath, Object returnObject, FuncBind funcBind) {
		if (MapUtils.isEmpty(funcBind.getPostHandle()) && (returnObject == null)) {
			return this.readApiStaticFileToString(request, servletPath);
		}

		Map<String, Object> returnMap = top.cardone.context.util.MapUtils.toMap(returnObject, this.objectsKey, this.objectKey);

		if (MapUtils.isEmpty(funcBind.getPostHandle())) {
			return returnMap;
		}

		for (Map.Entry<String, Object> postHandleEntry : funcBind.getPostHandle().entrySet()) {
			if (postHandleEntry.getValue() instanceof String) {
				returnMap.put(postHandleEntry.getKey(), ApplicationContextHolder.func(Func1.class, func1 -> func1.func(null), (String) postHandleEntry.getValue()));

				continue;
			}

			if (!(postHandleEntry.getValue() instanceof Map)) {
				continue;
			}

			Map<String, Object> postHandleEntryValue = (Map<String, Object>) postHandleEntry.getValue();

			if (MapUtils.isEmpty(postHandleEntryValue)) {
				break;
			}

			String beanId = MapUtils.getString(postHandleEntryValue, "beanId");

			if (StringUtils.isEmpty(beanId)) {
				for (Map.Entry<String, Object> postHandleEntryValueEntry : postHandleEntryValue.entrySet()) {
					Object func = ApplicationContextHolder.getBean(postHandleEntryValueEntry.getKey());

					if (func == null) {
						continue;
					}

					if (func instanceof Func0) {
						returnMap.put(postHandleEntry.getKey(), ((Func0) func).func());
					} else if (func instanceof Func1) {
						returnMap.put(postHandleEntry.getKey(), ((Func1) func).func(postHandleEntryValueEntry.getValue()));
					} else if (func instanceof Func2) {
						returnMap.put(postHandleEntry.getKey(), ((Func2) func).func(returnMap, postHandleEntryValueEntry.getValue()));
					}
				}

				continue;
			}

			Func1 func = ApplicationContextHolder.getBean(Func1.class, beanId);

			if (func == null) {
				continue;
			}

			returnMap.put(postHandleEntry.getKey(), func.func(MapUtils.getObject(postHandleEntryValue, "params")));
		}

		return returnMap;
	}

	/**
	 * 读取api静态文件
	 *
	 * @param request
	 * @param servletPath
	 * @return
	 */
	private String readApiStaticFileToString(HttpServletRequest request, String servletPath) {
		try {
			String apiStaticFilePath;

			if (StringUtils.isBlank(apiStaticRoot)) {
				apiStaticFilePath = WebUtils.getRealPath(request.getServletContext(), servletPath);
			} else {
				apiStaticFilePath = apiStaticRoot + servletPath;
			}

			Resource resource = ApplicationContextHolder.getApplicationContext().getResource(apiStaticFilePath);

			if (!resource.exists()) {
				return "{}";
			}

			try (InputStream is = resource.getInputStream()) {
				return IOUtils.toString(is, Charsets.UTF_8);
			}
		} catch (IOException e) {
			throw new CodeException("000011", new String[]{e.getMessage()}, e);
		}
	}
}